package com.jerry.boot.business.log;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jerry.boot.business.log.pojo.OprLogPojo;
import com.jerry.boot.business.user.User;
import com.jerry.boot.business.user.UserService;
import com.jerry.boot.core.Utils;
import com.jerry.boot.core.web.ServletContextHelper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.Date;

@Aspect
@Component
public class OprLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(OprLogAspect.class);

    private final OprLogService oprLogService;

    private final ObjectMapper objectMapper;

    private final UserService userService;

    public OprLogAspect(OprLogService oprLogService, ObjectMapper objectMapper, UserService userService) {
        this.oprLogService = oprLogService;
        this.objectMapper = objectMapper;
        this.userService = userService;
    }

    @Pointcut(value = "@annotation(com.jerry.boot.business.log.Log)")
    public void logPointcut() {
    }

    @Around(value = "logPointcut()&&@annotation(log)")
    public Object handleAround(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        Object result = null;
        Throwable throwable = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throwable = e;
        }
        if (log.sync()) {
            Throwable finalThrowable = throwable;
            Object finalResult = result;
            ThreadUtil.execAsync(() -> {
                try {
                    doLog(log, joinPoint, finalThrowable, finalResult);
                } catch (Exception e) {
                    logger.error("sync write log error", e);
                }
            });
        } else {
            try {
                doLog(log, joinPoint, throwable, result);
            } catch (Exception e) {
                logger.error("write log error", e);
            }
        }

        if (throwable != null) {
            throw throwable;
        }
        return result;
    }

    public void doLog(Log log, ProceedingJoinPoint joinPoint, Throwable throwable, Object result) {
        String expression = log.argExpression();
        int[] argIndexes = log.argIndexes();
        String content = log.value();
        Object[] args = joinPoint.getArgs();
        String api = getApi(joinPoint);
        String apiParams = "";
        if (StrUtil.isNotBlank(expression)) {
            apiParams = Utils.getExpressionStringValue(expression, args);
        } else if (ArrayUtil.isNotEmpty(argIndexes)) {
            for (int i = 0; i < argIndexes.length; i++) {
                int index = argIndexes[i];
                if (index < args.length) {
                    Object arg = args[index];
                    apiParams += arg + ";";
                }
            }
        }
        OprLogPojo oprLog = new OprLogPojo();
        oprLog.setCreateTime(new Date());
        oprLog.setOprApi(api);
        oprLog.setOprApiParams(apiParams);
        oprLog.setOprContent(content);
        oprLog.setOprIp(ServletUtil.getClientIP(ServletContextHelper.getRequest()));

        String oprUsername = ServletContextHelper.getLoginUsername();
        if (StrUtil.isNotBlank(oprUsername)) {
            User user = userService.getUserByUsername(oprUsername);
            oprLog.setId(user.getId());
            oprLog.setOprUsername(user.getUsername());
        }

        if (log.saveResp() && result != null) {
            try {
                String respBody = objectMapper.writeValueAsString(result);
                oprLog.setRespJson(respBody);
            } catch (JsonProcessingException e) {
                logger.error("write resp body error in log", e);
            }
        }

        if (throwable != null) {
            oprLog.setStacktrace(ExceptionUtil.stacktraceToString(throwable, 1000));
        }

        this.oprLogService.writeLog(oprLog);
    }

    private String getApi(ProceedingJoinPoint joinPoint) {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        if (method.isAnnotationPresent(GetMapping.class)) {
            return method.getAnnotation(GetMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(PostMapping.class)) {
            return method.getAnnotation(PostMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(DeleteMapping.class)) {
            return method.getAnnotation(DeleteMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(PutMapping.class)) {
            return method.getAnnotation(PutMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(RequestMapping.class)) {
            return method.getAnnotation(RequestMapping.class).value()[0];
        }
        logger.error("not match controller method found");
        return null;
    }

}
